﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using Cubicle.Core.Config;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Hosting;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Shared;

namespace Cubicle.Core
{

    /// <summary>
    /// When the 'InstanceContextMode' attribute is set to '.Single'
    /// the lifetime of the Service will automatically be managed by IIS
    /// and not forwarded to the appropriate ServiceFactory (in this case,
    /// Autofac). 
    /// 
    /// WCF will complain that there is no parameter-less constructor.
    /// 
    /// By setting the InstanceContextMode to .PerSession, the actual
    /// component lifetime is managed by the IoC container, where it is 
    /// set to SingleInstance, which effectively means lifetime management
    /// is handled by the IoC instead of WCF.
    /// </summary>
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single,
        Namespace = "http://www.cubicle.co.uk/",
        Name = "Engine")]
    //[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
    //[ReflectKnownType]
    //[EngineResolverBehavior]
    [UseNetDataContractSerializer]
    [Export(typeof(IEngine))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class Engine : MarshalByRefObject, IEngine
    {

        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        // Virtual variables
        [Export]
        public IComponentHost EngineHost { get; set; }

        [Import]
        public IRepository Repository { get; set; }

        [Import]
        public IInvoker Invoker { get; set; }

        [Import]
        public IScriptManager ScriptManager { get; set; }

        [Import]
        public IReflectionEngine Reflector { get; set; }

        [ImportingConstructor]
        public Engine(EngineConfig config)
        {
            EngineHost = new ComponentHost<IEngine>(this, config);
            Id = Environment.MachineName + System.Diagnostics.Process.GetCurrentProcess().Id;
        }

        public Engine() {} // Never use this constructor! Only here so this type can be hosted as a WCF service

        #region Overrides

        public override object InitializeLifetimeService()
        {
            // Give this object an infinite lifetime
            return null;
        }

        #endregion

        #region Implementation of IEngine:Hosting

        public string StartHost(HostingType type, bool enableMetaData, int port = 20101)
        {
            return EngineHost.StartHost(type, enableMetaData, port);
        }

        public bool StopHost()
        {
            return EngineHost.StopHost();
        }

        public bool IsHosted()
        {
            return EngineHost.IsHosted();
        }

        public string GetHostAddress()
        {
            return EngineHost.GetHostAddress();
        }

        #endregion

        #region Implementation of IEngine:Reflection

        public Dictionary<string, string> GetHandleTypes()
        {
            return Reflector.GetHandleTypes();
        }

        public Dictionary<string, string> GetMetaDataTypes(string handle)
        {
            return Reflector.GetMetaDataTypes(handle);
        }

        public List<MemberData> GetMembers(string handle)
        {
            return Reflector.GetMembers(handle);
        }

        public IEnumerable<AssemblyData> GetAssemblyDetails()
        {
            return Reflector.GetAssemblyDetails();
        }

        public TypeData GetTypeData(string target)
        {
            return Reflector.GetTypeData(target);
        }

        public List<TypeData> GetTypes(string fullname)
        {
            return Reflector.GetTypes(fullname);
        }

        public List<MethodData> GetMethods(string target)
        {
            return Reflector.GetMethods(target);
        }

        public List<FieldData> GetFields(string target)
        {
            return Reflector.GetFields(target);
        }

        public List<PropertyData> GetProperties(string target)
        {
            return Reflector.GetProperties(target);
        }

        public List<ConstructorData> GetConstructors(string target)
        {
            return Reflector.GetConstructors(target);
        }

        public List<ParameterData> GetParameterData(string target, string methodName, int offset)
        {
            return Reflector.GetParameterData(target, methodName, offset);
        }

        public void RemoveHandle(string handle)
        {
            Repository.Remove(handle);
        }

        public string LoadAssembly(byte[] assembly, byte[] symbols)
        {
            // Now load more assemblies
            return Reflector.LoadAssembly(assembly, symbols);
        }

        #endregion

        #region Implementation of IEngine:Scripting

        public bool RunByteScript(byte[] rawScript)
        {
            return ScriptManager.RunByteScript(rawScript);
        }

        #endregion

        #region Implementation of IEngine:Event handling

        /// <summary>
        /// This makes IEngineAware objects repo-aware by injecting references to the repo
        /// and the corresponding object key.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void MakeRepoAware(object sender, RepoEventArgs args)
        {
            var obj = args.TargetRObject.Object;
            if (!(obj is IRepoAware)) return;

            var temp = (IRepoAware)obj;
            temp.Wrapper.Handle = args.TargetRObject.Handle;
            temp.Wrapper.OnSet(this, args);
        }

        public event EventHandler ProcessorEvent = delegate {};

        public IProcessor Parent { get { return Invoker.Parent; } set { Invoker.Parent = value; } }

        public ProcessorConfig Config { get { return Invoker.Config; } set { Invoker.Config = value; } }

        public void RaiseProcessorEvent(object sender, EventArgs e)
        {
            Invoker.RaiseProcessorEvent(sender, e);
        }

        public void AddProcessor(CompositionContainer container, ProcessorConfig config)
        {
            Invoker.AddProcessor(container, config);
        }

        #endregion

        #region Implementation of IEngine:Misc

        public bool LoadRunnerState { get; set; }

        public bool GetLoadRunnerState() { return LoadRunnerState; }
        
        public string GetVersion()
        {
            return Assembly.GetExecutingAssembly().GetName().Version.ToString();
        }

        public void SetLoadRunnerState(bool state) { LoadRunnerState = state; }

        public IEnumerable<string> GetLog(int i)
        {
            return Helpers.GetMemoryLog(i);
        }

        protected void RunOnLoad(object sender, AssemblyLoadEventArgs args)
        {
            if (!LoadRunnerState) return;

            List<string> missing;
            var safeTypes = args.LoadedAssembly.SafeGetTypes(out missing, false);
            if (safeTypes == null || safeTypes.Count() == 0) return;

            var loadRunners = safeTypes
                    .Where(type => type.GetInterfaces().Contains(typeof(ILoadRunner)));

            foreach (var loadRunner in loadRunners)
            {
                try
                {
                    var runner = (ILoadRunner)Activator.CreateInstance(loadRunner);
                    runner.RunOnLoad(this);
                }
                catch (Exception e)
                {
                    _logger.ErrorException(e, "Error executing 'ILoadRunner.RunOnLoad'. Assembly = {0}, Type = {1}.",
                                  args.LoadedAssembly.FullName, loadRunner.FullName);
                }
            }
        }

        public string Id { get; set; }

        public string GetId() { return Id; }

        #endregion

        #region Implementation of IProcessorManager

        public object Process(object input)
        {
            return Invoker.Process(input);
        }

        public void Build(ProcessorConfig config)
        {
            Invoker.Build(config);
        }

        #endregion

        #region Implementation of IRepository

        public event EventHandler<RepoEventArgs> SetEvent = delegate {};
        public event EventHandler<RepoEventArgs> GetEvent = delegate { };
        public event EventHandler<RepoEventArgs> RemoveEvent = delegate { };

        public void Clear()
        {
            Repository.Clear();
        }

        public string[] List()
        {
            return Repository.List();
        }

        public string Rename(string oldHandle, string newKey)
        {
            return Repository.Rename(oldHandle, newKey);
        }

        public IRObject Remove(string handle)
        {
            return Repository.Remove(handle);
        }

        public bool DisposeRemove(string handle)
        {
            return Repository.DisposeRemove(handle);
        }

        public bool Has(string handle)
        {
            return Repository.Has(handle);
        }

        public string Set(string key, object item)
        {
            return Repository.Set(key, item);
        }

        public object Get(string handle)
        {
            return Repository.Get(handle);
        }

        public IRObject GetRObject(string handle)
        {
            return Repository.GetRObject(handle);
        }

        #endregion

        #region Implementation of IDisposable

        public void Dispose()
        {
            EngineHost.Dispose();
        }

        #endregion
    }
}